package GA;

import instrumentation.TrackerCoverage;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.*;

import mutation.Mutation;
import random.TestClusterBuilder;
import crossover.Crossover;


import Operations.*;
import util.*;

public class ReflectionExecutor {
	private static final Logger logger = Logger.getLogger("ReflectionExecutor");
	/** The internal object repository */
	private final Map<String, Object> repositoryObjects;
	
	Chromosome test;
	public List<Operation> preOperation= new LinkedList<Operation>();
	
	private final Map<String, String[]> repositoryType;
	private final Map<String, List<CreateObject>> constructors;
	private final TrackerCoverage track=	TrackerCoverage.getTracker();

	public static  int execute(GAInfo gaInfo,Chromosome test) {
		ReflectionExecutor executor = new ReflectionExecutor(test);

		return executor.execute(gaInfo);
	}

	public ReflectionExecutor(Chromosome test){
		repositoryType = test.getReferenceFactory();
		
		this.constructors=test.getConstructors();
		repositoryObjects = new LinkedHashMap<String, Object>();
		preOperation= new LinkedList<Operation>();
		this.test=test;

	}
	public int execute(GAInfo gaInfo) {

		long startTime=gaInfo.getStartTime();
		long TestTimeout=gaInfo.getTestTimeOut();
		long elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);

		final List<Operation> ops  = test.getTest();
		for(Operation op : ops ) {
			if (elapsed > TestTimeout) break;
			try {
	//			System.out.println("ReflectionExecutor.execute: "+ op.toString());
			if (op instanceof CreateObject) createReceiverObject( op);
			else if(op instanceof Invoke) invoke((Invoke) op,op);
				 else if(op instanceof AssignConstant) assignConstant((AssignConstant) op);
				 	  else logger.warning("ReflectionExecutor.execute: Unknown operation " + op.getClass().getName() + " - " + op);
			} catch(Throwable e) {
			//	System.err.println("ReflectionExecutor.execute:"+ e + op.getClass().getName() + " - " + op);
			}
//			if(preOperation.size()>0)
//				System.out.println(" preOperation: "+ preOperation.get(preOperation.size()-1).toString());
			elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);
			gaInfo.setNoExecution((gaInfo.getNoExecution()+1));
			if((gaInfo.getNoExecution()%100000==0) && GAConfig.SaveData) {
				Log.log("when " + gaInfo.getNoExecution() +" calls total coverage is " + track.getTotalCoveredBranch());
		}

		}
		return 0;
	}
	private void createReceiverObject(Operation op){
		 CreateObject cObj= (CreateObject)op;
		ConstructorDeclared cut = cObj.getConstructor();
		//remove all the constructor and methods call from the preOperation as there is a new constructor
	
		Object obj= createReceiverObject(cut);
	
		if(GAConfig.getAlgorithmName().equals("Memetic Algorithm"))//&&(obj!=null))
				this.shortTestCaseUnCoveredBranch(op);
	}
	private void assignConstant(AssignConstant op){
		Class<?> clz=op.getClazz();
		String ref=op.getRefrence();
		String value=op.getValue();
		if(value == null) {
			repositoryObjects.put(ref, null);
		}else{
			try{
				//Field field = declaringClass.getField(value.getName());
			Field field = clz.getField(value);
			Object newObject = field.get(null);
			repositoryObjects.put(ref, newObject);
		} catch(Throwable e) {
			//System.err.println("ReflectionExecutor.assignConstant :"+ e.getMessage());
		}

		}
	}
	private void invoke(Invoke op,Operation operation) throws Throwable {

		final String receiver = op.getThis();
		final MethodDeclared method = op.getMethod();
		final String[] params = op.getParams();
		final Class<?>[] paramsTypes = method.getParameterTypes();
		
		Object receiverObject = null;
		if (!method.isStatic()) {
			receiverObject = (Object) getReceiverObject(receiver);
			if (receiverObject==null){
				if(GAConfig.getAlgorithmName().equals("Memetic Algorithm"))
					this.shortTestCaseUnCoveredBranch(operation);

				return;
			}
		}
		// create parameters
		final Object[] args = new Object[params.length];
		for(int i = 0; i < args.length; i++) {
			Class<?> clz=paramsTypes[i];
			if(Filter.isObjectType(clz))
				 clz=method.getReplacementObjectParam();
			if(Filter.isPrimitiveOrStringType(clz))
				args[i] = ValuesGenerator.mapValueToObject(clz,params[i]);
			else 
				args[i]=(Object) getReceiverObject(params[i]);
		}
		// perform the method call
		Object result = null;

		try {
			// get the Method object
			final Method m = getMethod(method);

			result= m.invoke(receiverObject, args);
			if(result != null) 	repositoryObjects.put(op.get_return(), result);


		} catch (InvocationTargetException e) {
//			System.err.println(op.toString() + "InvocationTargetException."+ e.getCause());
//			return; // continues normal execution in case of exception in called
		}catch (IllegalAccessException e) {
			System.err.println("Illegal access error.");
			System.exit(1);
		}
		if(GAConfig.getAlgorithmName().equals("Memetic Algorithm"))
			this.shortTestCaseUnCoveredBranch(operation);

	}
	private void shortTestCaseUnCoveredBranch(Operation op){
		preOperation.add(op);
		List<Operation> ops = new LinkedList<Operation>();
		ops.addAll(preOperation);
		
		//Store MCS that hit the uncovered branch
		List<Integer> keys = new ArrayList<Integer>( track.branchNotCoveredByIndividual.keySet());   
		for( int i=0; i<keys.size();i++){
			int key=keys.get(i);
			if (track.branchNotCoveredByIndividual.containsKey(key)) {
				Object obj = track.branchNotCoveredByIndividual.get(key);
				if (obj == null) {
					Chromosome chrom = new Chromosome(this.test.getCluster(),
							this.test.getRefFactory(), constructors, ops);
					chrom.setBranchDistance(track.getTrackers().get(key).getQuality());
					track.branchNotCoveredByIndividual.put(key, chrom);
				}
					
			}
		}
		track.InitialTempBranchNotCovered();
	}
	public Method getMethod(MethodDeclared  m) throws ClassNotFoundException, SecurityException, NoSuchMethodException {

		Class<?> c = m.getClazz();
		Class<?>[] params = m.getParameterTypes();
		Method method = c.getMethod(m.getName(), params);

		return method;
	}

	private Object getReceiverObject(String objRef)  {
//		System.out.println("ReflectionExecutor.getReceiverObject: "+ objRef);

		Object obj = null;
		try {
			obj = repositoryObjects.get(objRef.toString());
			if (obj == null)
				obj = createReceiverObject(objRef);

		} catch (Throwable e) {

		}
		return obj;
	}
	/** createObject */
	private Object createReceiverObject(String objRef)  {
		Object obj = null;
		String cName=null;
		List<String> keys = new ArrayList<String>(repositoryType.keySet());   
		for(int i=0;i<keys.size()&&cName==null;i++){
			//repositoryType.get(keys.get(i)).toString();   
			String[] references = repositoryType.get(keys.get(i));
			Arrays.sort(references);
	    	if( Arrays.binarySearch(references, objRef)>=0)
				 cName =(String) keys.get(i).toString();
		}
		if(cName==null)	{
			System.out.println("Error -->ReflectionExecutor.createReceiverObject "+objRef + " is null");
			System.exit(0);
		}
		ConstructorDeclared cut=null;
		List<CreateObject> cutConst=constructors.get(cName);
		if(cutConst==null) return null;
			for(CreateObject Const: cutConst){
				cut= Const.getConstructor();
				if(cut.getRefrence().equals(objRef))
					break;
				else
					cut=null;
			}
		//int randNum=ValuesGenerator.getRandomInt(cutConst.length);
		// cut= cutConst[randNum];
		Class<?>[] paramsType = cut.getParameterTypes();
		Object[] actualParams = new Object[cut.getParameterTypes().length];
		String[] params = cut.getParams();
//		String[] p =cut.getParams();// new String[paramsType.length];
		for (int j = 0; j < actualParams.length; j++)
			if(Filter.isPrimitiveOrStringType(paramsType[j]))
				//	actualParams[j] = ValuesGenerator.mapValueToObject(paramsType[j],params[j]);
				actualParams[j] = ValuesGenerator.mapValueToObject(paramsType[j],params[j]);
				else 
					actualParams[j]=(Object) getReceiverObject(params[j]);

		// get the Constructor object
		Constructor<?> cons=cut.getCons();
		try {
			obj = cons.newInstance(actualParams);

		} catch (Throwable e) {
		//	System.err.println("ReflectionExecutor.createReceiverObject execute Object Construction class: "+ cName+"  "+ e.getCause());
			//System.exit(1);
		}
		if(obj!=null)
			repositoryObjects.put(objRef, obj);
		

		return obj;

	}
	/** createObject from operation!*/
	private Object createReceiverObject(ConstructorDeclared cut)  {
		String objRef=cut.getRefrence();
		Object obj = null;
		
		Class<?>[] paramsType = cut.getParameterTypes();
		Object[] actualParams = new Object[cut.getParameterTypes().length];
		String[] params = cut.getParams();
		for (int j = 0; j < actualParams.length; j++){
			if(Filter.isPrimitiveOrStringType(paramsType[j]))
			//	actualParams[j] = ValuesGenerator.mapValueToObject(paramsType[j],params[j]);
			actualParams[j] = ValuesGenerator.mapValueToObject(paramsType[j],params[j]);
			else 
				actualParams[j]=(Object) getReceiverObject(params[j]);

		}

		// get the Constructor object
		Constructor<?> cons=cut.getCons();
		try {
			obj = cons.newInstance(actualParams);

		} catch (Throwable e) {
		//	System.err.println("ReflectionExecutor.createReceiverObject execute Object Construction class: "+ cName+"  "+ e.getCause());
			//System.exit(1);
		}
		if(obj!=null)
			repositoryObjects.put(objRef, obj);
		
		return obj;

	}
	public static String getReference(String cName,Map<String, String[]>  refFactory) {

		String refs[] = refFactory.get(cName);

		if(refs == null) return null;

		return refs[ValuesGenerator.getRandomInt(refs.length)];
	}
	public static GAInfo     gaInfo     = new GAInfo();
	public static void main(String[] args) {
		try {
			long startTime = Calendar.getInstance().getTimeInMillis();
			long elapsed = Calendar.getInstance().getTimeInMillis() - startTime;
			gaInfo.setTestTimeOut(60 * 1000*10);
			gaInfo.setStartTime(startTime);

				ValuesGenerator.resetSeed(10);
				
			Crossover crossoverStrategy = GAOperationsConfig.getCrossoverStrategy();
			Mutation ChangeValueStrategy = GAOperationsConfig.getMutationChangeValueStrategy();
			Mutation MethodInsertStrategy = GAOperationsConfig.getMutationMethodInsertStrategy();
			Mutation MethodDeleteStrategy = GAOperationsConfig.getMutationMethodDeleteStrategy();

			System.out.println("CUT is:" + GAConfig.classFilePath);
			 Set<Class<?>> allClasses = new LinkedHashSet<Class<?>>();
		      //get class from file
		      allClasses.addAll(TestClusterBuilder.readClassFromFile());

		      System.out.println("There are " + allClasses.size() + " classes as input");
		      
		      System.out.println(allClasses);
		      Set<Class<?>> classesToTest = TestClusterBuilder.filterUntestableClasses(allClasses);
		      System.out.println("There are " + classesToTest.size() + " classes to test after filtering");
		      System.out.println(classesToTest);

			// calculate the test cluster and the set of types involved in the
				TestClusterBuilder TestCluster = new TestClusterBuilder(classesToTest);		
		//execute the chiromosme;

			
			String input;
			BufferedReader in = new BufferedReader(new InputStreamReader(
					System.in));

	//		while ((input = in.readLine()) != null) {
				Chromosome chrom1=ChromosomeFormer.buildNewChromosome(TestCluster);
				System.out.println(chrom1.toString());

				ReflectionExecutor.execute( gaInfo,chrom1);
//			}
			System.exit(0);
		} catch (Throwable e) {
			System.err.println("chromFormer.main: " + e.getMessage());
	//		System.exit(1);
		}

	}
}
